Passed
Push — master ( f30a6d...1b8a37 )
by Jan
05:37
created

AjaxUI.fillTrees   B

Complexity

Conditions 6

Size

Total Lines 51
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 40
c 0
b 0
f 0
dl 0
loc 51
rs 7.9866

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
/*
2
 *
3
 * part-db version 0.1
4
 * Copyright (C) 2005 Christoph Lechner
5
 * http://www.cl-projects.de/
6
 *
7
 * part-db version 0.2+
8
 * Copyright (C) 2009 K. Jacobs and others (see authors.php)
9
 * http://code.google.com/p/part-db/
10
 *
11
 * Part-DB Version 0.4+
12
 * Copyright (C) 2016 - 2019 Jan Böhmer
13
 * https://github.com/jbtronics
14
 *
15
 * This program is free software; you can redistribute it and/or
16
 * modify it under the terms of the GNU General Public License
17
 * as published by the Free Software Foundation; either version 2
18
 * of the License, or (at your option) any later version.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 * GNU General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU General Public License
26
 * along with this program; if not, write to the Free Software
27
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
28
 *
29
 */
30
31
import * as Cookies from "js-cookie";
32
33
/**
34
 * Extract the title (The name between the <title> tags) of a HTML snippet.
35
 * @param {string} html The HTML code which should be searched.
36
 * @returns {string} The title extracted from the html.
37
 */
38
function extractTitle(html : string) : string {
39
    let title : string = "";
40
    let regex = /<title>(.*?)<\/title>/gi;
41
    if (regex.test(html)) {
42
        let matches = html.match(regex);
43
        for(let match in matches) {
44
            title = $(matches[match]).text();
45
        }
46
    }
47
    return title;
48
}
49
50
51
class AjaxUI {
52
53
    protected BASE = "/";
54
55
    private trees_filled : boolean = false;
56
57
    private statePopped : boolean = false;
58
59
    public xhr : XMLHttpRequest;
60
61
    public constructor()
62
    {
63
        //Make back in the browser go back in history
64
        window.onpopstate = this.onPopState;
65
        $(document).ajaxError(this.onAjaxError.bind(this));
66
        //$(document).ajaxComplete(this.onAjaxComplete.bind(this));
67
    }
68
69
    /**
70
     * Starts the ajax ui und execute handlers registered in addStartAction().
71
     * Should be called in a document.ready, after handlers are set.
72
     */
73
    public start(disabled : boolean = false)
74
    {
75
        if(disabled) {
76
            return;
77
        }
78
79
        console.info("AjaxUI started!");
80
81
        this.BASE = $("body").data("base-url") + "/";
82
        console.info("Base path is " + this.BASE);
83
84
        //Show flash messages
85
        $(".toast").toast('show');
86
87
88
        /**
89
         * Save the XMLHttpRequest that jQuery used, to the class, so we can acess the responseURL property.
90
         * This is a work-around as long jQuery does not implement this property in its jQXHR objects.
91
         */
92
        //@ts-ignore
93
        jQuery.ajaxSettings.xhr = function () {
94
            //@ts-ignore
95
            let xhr = new window.XMLHttpRequest();
96
            //Save the XMLHttpRequest to the class.
97
            ajaxUI.xhr = xhr;
98
            return xhr;
99
        };
100
101
102
        this.registerLinks();
103
        this.registerForm();
104
        this.fillTrees();
105
106
        this.initDataTables();
107
108
        //Trigger start event
109
        $(document).trigger("ajaxUI:start");
110
    }
111
112
    /**
113
     * Fill the trees with the given data.
114
     */
115
    public fillTrees()
116
    {
117
        let categories =  Cookies.get("tree_datasource_tree-categories");
118
        let devices =  Cookies.get("tree_datasource_tree-devices");
119
        let tools =  Cookies.get("tree_datasource_tree-tools");
120
121
        if(typeof categories == "undefined") {
122
            categories = "categories";
123
        }
124
125
        if(typeof devices == "undefined") {
126
            devices = "devices";
127
        }
128
129
        if(typeof tools == "undefined") {
130
            tools = "tools";
131
        }
132
133
        this.treeLoadDataSource("tree-categories", categories);
134
        this.treeLoadDataSource("tree-devices", devices);
135
        this.treeLoadDataSource("tree-tools", tools);
136
137
        this.trees_filled = true;
138
139
        //Register tree btns to expand all, or to switch datasource.
140
        $(".tree-btns").click(function (event) {
141
            event.preventDefault();
142
            $(this).parents("div.dropdown").removeClass('show');
143
            //$(this).closest(".dropdown-menu").removeClass('show');
144
            $(".dropdown-menu.show").removeClass("show");
145
            let mode = $(this).data("mode");
146
            let target = $(this).data("target");
147
            let text = $(this).text() + " \n<span class='caret'></span>"; //Add caret or it will be removed, when written into title
148
149
            if (mode==="collapse") {
150
                // @ts-ignore
151
                $('#' + target).treeview('collapseAll', { silent: true });
152
            }
153
            else if(mode==="expand") {
154
                // @ts-ignore
155
                $('#' + target).treeview('expandAll', { silent: true });
156
            } else {
157
                Cookies.set("tree_datasource_" + target, mode);
158
                ajaxUI.treeLoadDataSource(target, mode);
159
            }
160
161
            return false;
162
        });
163
    }
164
165
    /**
166
     * Load the given url into the tree with the given id.
167
     * @param target_id
168
     * @param datasource
169
     */
170
    protected treeLoadDataSource(target_id, datasource) {
171
        let text : string = $(".tree-btns[data-mode='" + datasource + "']").html();
172
        text = text + " \n<span class='caret'></span>"; //Add caret or it will be removed, when written into title
173
        switch(datasource) {
174
            case "categories":
175
                ajaxUI.initTree("#" + target_id, 'tree/categories');
176
                break;
177
            case "locations":
178
                ajaxUI.initTree("#" + target_id, 'tree/locations');
179
                break;
180
            case "footprints":
181
                ajaxUI.initTree("#" + target_id, 'tree/footprints');
182
                break;
183
            case "manufacturers":
184
                ajaxUI.initTree("#" + target_id, 'tree/manufacturers');
185
                break;
186
            case "suppliers":
187
                ajaxUI.initTree("#" + target_id, 'tree/suppliers');
188
                break;
189
            case "tools":
190
                ajaxUI.initTree("#" + target_id, 'tree/tools');
191
                break;
192
            case "devices":
193
                ajaxUI.initTree("#" + target_id, 'tree/devices');
194
                break;
195
        }
196
197
        $( "#" + target_id + "-title").html(text);
198
    }
199
200
    /**
201
     * Fill a treeview with data from the given url.
202
     * @param tree The Jquery selector for the tree (e.g. "#tree-tools")
203
     * @param url The url from where the data should be loaded
204
     */
205
    public initTree(tree, url) {
206
        //let contextmenu_handler = this.onNodeContextmenu;
207
        $.getJSON(ajaxUI.BASE + url, function (data) {
208
            // @ts-ignore
209
            $(tree).treeview({
210
                data: data,
211
                enableLinks: false,
212
                showIcon: false,
213
                showBorder: true,
214
                searchResultBackColor: '#ffc107',
215
                searchResultColor: '#000',
216
                onNodeSelected: function(event, data) {
217
                    if(data.href) {
218
                        ajaxUI.navigateTo(data.href);
219
                    }
220
                },
221
                //onNodeContextmenu: contextmenu_handler,
222
                expandIcon: "fas fa-plus fa-fw fa-treeview", collapseIcon: "fas fa-minus fa-fw fa-treeview"})
223
                .on('initialized', function() {
224
                    $(this).treeview('collapseAll', { silent: true });
225
226
                    //Implement searching if needed.
227
                    if($(this).data('treeSearch')) {
228
                        let _this = this;
229
                        let $search = $($(this).data('treeSearch'));
230
                        $search.on( 'input', function() {
231
                            $(_this).treeview('collapseAll', { silent: true });
232
                            $(_this).treeview('search', [$search.val()]);
233
                        });
234
                    }
235
                });
236
        });
237
    }
238
239
240
    /**
241
     * Register all links, for loading via ajax.
242
     */
243
    public registerLinks()
244
    {
245
        // Unbind all old handlers, so the things are not executed multiple times.
246
        $('a').not(".link-external, [data-no-ajax], .page-link, [href^='#']").unbind('click').click(function (event) {
247
                let a = $(this);
248
                let href = $.trim(a.attr("href"));
249
                //Ignore links without href attr and nav links ('they only have a #)
250
                if(href != null && href != "" && href.charAt(0) !== '#') {
251
                    event.preventDefault();
252
                    ajaxUI.navigateTo(href);
253
                }
254
            }
255
        );
256
        console.debug('Links registered!');
257
    }
258
259
    protected getFormOptions() : JQueryFormOptions
260
    {
261
        return  {
262
            success: this.onAjaxComplete,
263
            beforeSerialize: function() : boolean {
264
265
                //Update the content of textarea fields using CKEDITOR before submitting.
266
                //@ts-ignore
267
                if(typeof CKEDITOR !== 'undefined') {
268
                    //@ts-ignore
269
                    for (let name in CKEDITOR.instances) {
270
                        //@ts-ignore
271
                        CKEDITOR.instances[name].updateElement();
272
                    }
273
                }
274
275
                return true;
276
            },
277
            beforeSubmit: function (arr, $form, options) : boolean {
278
                //When data-with-progbar is specified, then show progressbar.
279
                if($form.data("with-progbar") != undefined) {
280
                    ajaxUI.showProgressBar();
281
                }
282
                return true;
283
            }
284
        };
285
    }
286
287
    /**
288
     * Register all forms for loading via ajax.
289
     */
290
    public registerForm()
291
    {
292
293
        let options = this.getFormOptions();
294
295
        $('form').not('[data-no-ajax]').ajaxForm(options);
296
297
        console.debug('Forms registered!');
298
    }
299
300
301
    /**
302
     * Submits the given form via ajax.
303
     * @param form The form that will be submmitted.
304
     */
305
    public submitForm(form)
306
    {
307
        let options = ajaxUI.getFormOptions();
308
309
        $(form).ajaxSubmit(options);
310
    }
311
312
313
    /**
314
     * Show the progressbar
315
     */
316
    public showProgressBar()
317
    {
318
        //Blur content background
319
        $('#content').addClass('loading-content');
320
321
        // @ts-ignore
322
        $('#progressModal').modal({
323
            keyboard: false,
324
            backdrop: false,
325
            show: true
326
        });
327
    }
328
329
    /**
330
     * Hides the progressbar.
331
     */
332
    public hideProgressBar()
333
    {
334
        // @ts-ignore
335
        $('#progressModal').modal('hide');
336
        //Remove the remaining things of the modal
337
        $('.modal-backdrop').remove();
338
        $('body').removeClass('modal-open');
339
        $('body, .navbar').css('padding-right', "");
340
341
    }
342
343
344
    /**
345
     * Navigates to the given URL
346
     * @param url The url which should be opened.
347
     * @param show_loading Show the loading bar during loading.
348
     */
349
    public navigateTo(url : string, show_loading : boolean = true)
350
    {
351
        if(show_loading) {
352
            this.showProgressBar();
353
        }
354
        $.ajax(url, {
355
            success: this.onAjaxComplete
356
        });
357
        //$.ajax(url).promise().done(this.onAjaxComplete);
358
    }
359
360
    /**
361
     * Called when an error occurs on loading ajax. Outputs the message to the console.
362
     */
363
    private onAjaxError (event, request, settings) {
364
        'use strict';
365
        //Ignore aborted requests.
366
        if (request.statusText =='abort') {
367
            return;
368
        }
369
370
        console.error("Error getting the ajax data from server!");
371
        console.log(event);
372
        console.log(request);
373
        console.log(settings);
374
375
        ajaxUI.hideProgressBar();
376
377
        //Create error text
378
        let title = "";
379
380
        switch(request.status) {
381
            case 500:
382
               title =  'Internal Server Error!';
383
               break;
384
            case 404:
385
                title = "Site not found!";
386
                break;
387
            case 403:
388
                title = "Permission denied!";
389
                break;
390
        }
391
392
       var alert = bootbox.alert(
393
            {
394
                size: 'large',
395
                message: function() {
396
                    let msg = "Error getting data from Server! <b>Status Code: " + request.status + "</b>";
397
398
                    msg += '<br><br><a class=\"btn btn-link\" data-toggle=\"collapse\" href=\"#iframe_div\" >' + 'Show response' + "</a>";
399
                    msg += "<div class=\" collapse\" id='iframe_div'><iframe height='512' width='100%' id='iframe'></iframe></div>";
400
401
                    return msg;
402
                },
403
                title: title,
404
                callback: function () {
405
                    //Remove blur
406
                    $('#content').removeClass('loading-content');
407
                }
408
409
            });
410
411
        //@ts-ignore
412
        alert.init(function (){
413
            var dstFrame = document.getElementById('iframe');
414
        //@ts-ignore
415
        var dstDoc = dstFrame.contentDocument || dstFrame.contentWindow.document;
416
        dstDoc.write(request.responseText);
417
        dstDoc.close();
418
        });
419
420
421
422
        //If it was a server error and response is not empty, show it to user.
423
        if(request.status == 500 && request.responseText !== "")
424
        {
425
            console.log("Response:" + request.responseText);
426
        }
427
    }
428
429
    /**
430
     * This function gets called every time, the "back" button in the browser is pressed.
431
     * We use it to load the content from history stack via ajax and to rewrite url, so we only have
432
     * to load #content-data
433
     * @param event
434
     */
435
    private onPopState(event)
436
    {
437
        let page : string = location.href;
438
        ajaxUI.statePopped = true;
439
        ajaxUI.navigateTo(page);
440
    }
441
442
    /**
443
     * This function takes the response of an ajax requests, and does the things we need to do for our AjaxUI.
444
     * This includes inserting the content and pushing history.
445
     * @param responseText
446
     * @param textStatus
447
     * @param jqXHR
448
     */
449
    private onAjaxComplete(responseText: string, textStatus: string, jqXHR: any)
450
    {
451
        console.debug("Ajax load completed!");
452
453
454
        ajaxUI.hideProgressBar();
455
456
        /* We need to do the url checking before the parseHTML, so that we dont get wrong url name, caused by scripts
457
           in the new content */
458
        // @ts-ignore
459
        let url = this.url;
460
        //Check if we were redirect to a new url, then we should use that as new url.
461
        if(ajaxUI.xhr.responseURL) {
462
            url = ajaxUI.xhr.responseURL;
463
        }
464
465
466
        //Parse response to DOM structure
467
        //We need to preserve javascript, so the table ca
468
        let dom = $.parseHTML(responseText, document, true);
469
        //And replace the content container
470
        $("#content").replaceWith($("#content", dom));
471
        //Replace login menu too (so everything is up to date)
472
        $("#login-content").replaceWith($('#login-content', dom));
473
474
        //Replace flash messages and show them
475
        $("#message-container").replaceWith($('#message-container', dom));
476
        $(".toast").toast('show');
477
478
        //Set new title
479
        let title  = extractTitle(responseText);
480
        document.title = title;
481
482
        //Push to history, if we currently arent poping an old value.
483
        if(!ajaxUI.statePopped) {
484
            history.pushState(null, title, url);
485
        } else {
486
            //Clear pop state
487
            ajaxUI.statePopped = true;
488
        }
489
490
        //Do things on the new dom
491
        ajaxUI.registerLinks();
492
        ajaxUI.registerForm();
493
        ajaxUI.initDataTables();
494
495
        //Trigger reload event
496
        $(document).trigger("ajaxUI:reload");
497
    }
498
499
    /**
500
     * Init all datatables marked with data-datatable based on their data-settings attribute.
501
     */
502
    protected initDataTables()
503
    {
504
        //Find all datatables and init it.
505
        let $tables = $('[data-datatable]');
506
        $.each($tables, function(index, table) {
507
            let $table = $(table);
508
            let settings = $table.data('settings');
509
510
            //@ts-ignore
511
            var promise = $('#part_list').initDataTables(settings,
512
                {
513
                    "fixedHeader": { header: $(window).width() >= 768, //Only enable fixedHeaders on devices with big screen. Fixes scrolling issues on smartphones.
514
                        headerOffset: $("#navbar").height()}
515
                });
516
517
            //Register links.
518
            promise.then(function() {
519
                ajaxUI.registerLinks();
520
521
                //Set the correct title in the table.
522
                let title = $('#part-card-header-src');
523
                $('#part-card-header').html(title.html());
524
525
                //Attach event listener to update links after new page selection:
526
                $('#dt').on('draw.dt', function() {
527
                    ajaxUI.registerLinks();
528
                });
529
            });
530
        });
531
532
533
        console.debug('Datatables inited.');
534
    }
535
}
536
537
export let ajaxUI = new AjaxUI();